import store from "../store/index";
import { JxbHttpInterceptor } from "../data/session/session";

/**
 * Http客户端类
 */
export default class HttpClient {
    /**
     * @returns {string} 服务器接口地址
     */
    get url(){
        return `https://${this._data.domainName}/${this._data.api}`
    }
    /**
     * 设置域名
     * 
     * @param {string} domainName 
     * @returns {this}
     */
    setDomainName(domainName){
        this._data.domainName = domainName
        return this
    }
    /**
     * 设置服务器接口地址
     * 
     * 不含域名信息
     * @param {string} api 服务器接口地址
     * @returns {this}
     */
    setApiPath(api){
        this._data.api = api
        return this
    }
    /**
     * @returns {string|object|ArrayBuffer} 请求的参数
     */
    get body(){
        if(typeof this._data.body === 'undefined'){
            this._data.body = {}
        }
        return this._data.body
    }
    /**
     * 设置请求正文
     * 
     * **用法**
     * 
     * ```js
     * // 设置请求正文参数
     * client.setBody({foo:'bar',foo1:'bar1'})
     * // 仅添加一条参数
     * client.setBody('foo2','bar2')
     * ```
     * 
     * @param {*} body 请求的数据（value为undefined时）或者请求数据单条参数的参数名 （value不为undefined时）
     * @param {*} value [value = undefined] 请求数据单条参数的数值
     * @returns {this}
     */
    setBody(body, value = undefined){
        if(typeof value === 'undefined'){
            Object.keys(body).forEach(key=>{ // undefined的值全部转为空字符串
                if(typeof body[key] === 'undefined'){
                    body[key] = ''
                }
            })
            this._data.body = body
        }else{
            if(typeof this._data.body !== 'object') this._data.body = {}
            this._data.body[body] = value
        }
        return this
    }
    /**
     * @returns {object} 请求的 header
     */
    get header(){
        if(typeof this._data.header !== 'object'){
            this._data.header = {
                'Content-Type': 'application/json; charset=UTF-8'
            }
        }
        return this._data.header
    }
    /**
     * 设置请求头
     * 
     * **用法**
     * 
     * ```js
     * // 设置多条请求头
     * client.setHeader({foo:'bar',foo1:'bar1'})
     * // 仅添加一条请求头
     * client.setHeader('foo2','bar2')
     * ```
     * @param {object} header 请求的 header请求的数据（value为undefined时）或者请求头单条参数的参数名 （value不为undefined时）
     * @param {*} value [value = undefined] 请求头单条参数的数值
     * @returns {this}
     */
    setHeader(header, value = undefined){
        if(typeof value === 'undefined'){
            if(typeof header !== 'object') header = {}
            this._data.header = header
        }else{
            let newHeader = this.header // 已设置的data需要从getter中获取，而不应直接读取this._data.header
            newHeader[header] = value
            this._data.header = newHeader
        }
        return this
    }
    /**
     * @returns {string} HTTP 请求方法
     */
    get method(){
        if(typeof this._data.method !== 'string'){
            this._data.method = 'GET'
        }
        return this._data.method.toUpperCase()
    }
    /**
     * 
     * @param {string} method 
     * @returns {this}
     */
    setMethod(method){
        this._data.method = method
        return this
    }
    /**
     * @returns {string} 返回的数据格式
     */
    get dataType(){
        if(!this._data.dataType){
            this._data.dataType = 'json'
        }
        return this._data.dataType
    }
    /**
     * 
     * @param {string} dataType 返回的数据格式
     * @returns {this}
     */
    setDataType(dataType){
        this._data.dataType = dataType
        return this
    }
    /**
     * @returns {string} 返回的数据格式
     */
    get responseType(){
        if(!this._data.responseType){
            this._data.responseType = 'text'
        }
        return this._data.responseType
    }
    /**
     * 
     * @param {string} responseType 响应的数据类型
     * @returns {this}
     */
    setResponseType(responseType){
        this._data.responseType = responseType
        return this
    }
    /**
     * @returns {function} 接口调用成功的回调函数
     */
    get success(){
        return this._data.success
    }
    /**
     * @param {function} callback 接口调用成功的回调函数
     * @returns {this}
     */
    setSuccessCall(callback){
        this._data.success = callback
        return this
    }
    /**
     * @returns {function} 接口调用失败的回调函数
     */
    get fail(){
        return this._data.fail
    }
    /**
     * @param {function} callback 接口调用失败的回调函数
     * @returns {this}
     */
    setFailCall(callback){
        this._data.fail = callback
        return this
    }
    /**
     * @returns {function} 接口调用结束的回调函数（调用成功、失败都会执行）
     */
    get complete(){
        return this._data.complete
    }
    /**
     * @param {function} callback 接口调用结束的回调函数（调用成功、失败都会执行）
     * @returns {this}
     */
    setCompleteCall(callback){
        this._data.complete = callback
        return this
    }
    /**
     * 拦截的事件
     *
     * @readonly
     * @memberof HttpClient
     */
    get interceptBizEvent(){
        if(typeof this._data.interceptBizEvent === 'undefined'){
            this._data.interceptBizEvent = JxbHttpInterceptor.EVENT_ALL
        }
        return this._data.interceptBizEvent;
    }
    set interceptBizEvent(events){
        if(typeof events !== 'number'){
            console.error('拦截事件设置失败，必须传入整数值', events)
        }else{
            this._data.interceptBizEvent = parseInt(events)
        }
    }
    /**
     * 请求所需的参数
     * 
     * 可直接用于wx.request()
     * @returns {object}
     */
    get config(){
        let config = {}
        if (this._data.api) config.url = this.url
        if (this.body) config.data = this.body
        if (this.header) config.header = this.header
        if (this.method) config.method = this.method
        if (this.dataType) config.dataType = this.dataType
        if (this.responseType) config.responseType = this.responseType
        if (this.success) config.success = this.success
        if (this.fail) config.fail = this.fail
        if (this.complete) config.complete = this.complete
        config.interceptBizEvent = this.interceptBizEvent
        return config
    }

    constructor(){
        this._data = {}
        this._data.domainName = store.data.requestDomain
    }

    /**
     * 参数配置
     * 
     * @param {object} config 格式与wx.request()的参数一致
     * @returns {this}
     */
    setConfig(config){
        if(typeof config === 'object'){
            if(config.data) this.setBody(config.data)
            if(config.url){
                let hasHostPattern = /^(https?\:\/\/)?(?:[a-zA-Z0-9_-]+\.)+[a-zA-Z0-9_-]+(\:\d+)?\//,
                withHostPattern = /^(?:https?\:\/\/)?([a-zA-Z0-9._-]+(?:\:\d+)?)\/(.*)$/
                if(hasHostPattern.test(config.url)){
                    let matches = withHostPattern.exec(config.url)
                    if(typeof matches !== 'object'){
                        throw new WxRequestError('url格式不正确：'+config.url)
                    }
                    this.setDomainName(matches[1]).setApiPath(matches[2])
                }else{
                    this.setApiPath(config.url)
                }
            }
            if(config.header) this.setHeader(config.header)
            if(config.method) this.setMethod(config.method)
            if(config.dataType) this.setDataType(config.dataType)
            if(config.responseType) this.setResponseType(config.responseType)
            if(config.success) this.setSuccessCall(config.success)
            if(config.fail) this.setFailCall(config.fail)
            if(config.complete) this.setCompleteCall(config.complete)
            if(config.interceptBizEvent){
                this.interceptBizEvent = config.interceptBizEvent
            }
        }
        return this
    }

    /**
     * 发起请求
     * 
     * @param {object} config
     * @param {string} config.url 支持带域名和不带域名两种形式
     * @param {string} config.data
     * @param {string} config.method
     * @param {string} config.header
     * @param {requestSuccess} config.success
     * @param {requestFail} config.fail
     * @param {number} config.interceptBizEvent 拦截事件
     * @template RequestTask 网络请求任务对象
     * @returns {RequestTask} 返回RequestTask对象
     */
    request(config) {
        if(typeof config === 'object'){
            this.setConfig(config)
        }
        return wx.request(this.config)
    }

}
/**
 * http响应类
 */
export class HttpResponse
{
    /**
     * @returns {object} wx网络请求的原始响应值 
     */
    get rawResp(){
        return this._data
    }
    
    /**
     * @returns {(string|object|ArrayBuffer)} 响应正文
     */
    get body(){
        return this._data.data
    }
    /**
     * @returns {number} 响应状态码
     */
    get code(){
        return parseInt(this._data.statusCode)
    }
    /**
     * @returns {object} 响应头
     */
    get header(){
        return this._data.header
    }

    /**
     * 请求是否完成(code 200)
     * 
     * @returns {boolean} true = 服务器正确响应
     */
    isSucceeded(){
        return this.code === 200
    }

    /**
     * @param {object} rawResp wx.request回调的结果
     * @param {object} rawResp.header 
     * @param {string|object|ArrayBuffer} rawResp.data 
     * @param {number} rawResp.statusCode 
     */
    constructor(rawResp){
        this._data = rawResp
    }
}
/**
 * 酒信宝服务响应结果
 */
export class JxbResponse
{
    /**
     * @private {object|string|ArrayObject} 接口返回结果
     */
    _data

    /**
     * 原始的响应结果
     * @returns {object|string|ArrayObject}
     */
    get rawResp(){
        return this._data
    }
    /**
     * 结果描述
     * @returns {string}
     */
    get message(){
        if(typeof this._data.msg === 'undefined'){
            return this._data.message
        }else{
            return this._data.msg
        }
    }
    /**
     * 错误状态码，结果状态码，对应原始的errorCode或result字段
     * @returns {number}
     */
    get errorCode(){
        if(typeof this._data.errorCode !== 'undefined'){
            return parseInt(this._data.errorCode)
        }else if(typeof this._data.error !== 'undefined'){
            return parseInt(this._data.error)
        }else{
            return parseInt(this._data.result)
        }
    }
    /**
     * 响应的数据正文，对应原始的data字段
     * @returns {*}
     */
    get data(){
        return this._data.data
    }
    /**
     * 业务逻辑处理状态
     * 
     * 对应对应原始的result字段是否为0
     * @todo 部分接口返回的1为成功，部分接口返回的1为失败，所以对于接口使用1来表示成功与否时不要使用该方法
     * @returns {boolean} true = 业务逻辑已正确处理
     */
    isOk(){
        return this.errorCode === 0
    }
    /**
     * @param {object} rawResp http响应正文 httpResponse.body
     */
    constructor(rawResp){
        this._data = rawResp
    }
}
/**
 * 请求错误基类
 * @param {string} message 
 * @param {*} extra 
 */
export function WxRequestError(message, extra) {
    this.name = 'WxRequestError'
    this.message = message || '请求错误'
    this.stack = (new Error()).stack
    this.extra = extra
}
WxRequestError.prototype = Object.create(Error.prototype)
WxRequestError.prototype.constructor = WxRequestError
/**
 * 网络错误
 * 
 * 客户端网络问题或者服务器端没响应
 * @param {string} message
 * @param {Object} failResponse 
 */
export function NetworkError(message, failResponse) {
    this.name = 'NetworkError'
    this.failResponse = failResponse
    this.message = message || '网络错误'
    this.stack = (new WxRequestError(message, failResponse)).stack
}
NetworkError.prototype = Object.create(WxRequestError.prototype)
NetworkError.prototype.constructor = NetworkError
/**
 * 服务器响应错误
 * 
 * http状态码不是200
 * @param {string} message
 * @param {HttpResponse} httpResponse 
 */
export function JxbResponseError(message, httpResponse) {
    this.name = 'JxbResponseError'
    this.httpResponse = httpResponse
    this.message = message || '服务器响应错误'
    this.stack = (new WxRequestError(message, httpResponse)).stack
}
JxbResponseError.prototype = Object.create(WxRequestError.prototype)
JxbResponseError.prototype.constructor = JxbResponseError
/**
 * 业务逻辑错误
 * 
 * @param {string} message 
 * @param {number} errorCode
 * @param {JxbResponse} jxbResponse
 * @param {HttpResponse} httpResponse
 */
export function BusinessLogicError(message, errorCode, jxbResponse, httpResponse = undefined) {
    this.name = 'BusinessLogicError'
    this.message = message || '业务逻辑错误'
    this.stack = (new WxRequestError(message, httpResponse)).stack
    this.errorCode = errorCode
    this.jxbResponse = jxbResponse
    this.httpResponse = httpResponse
}
BusinessLogicError.prototype = Object.create(WxRequestError.prototype)
BusinessLogicError.prototype.constructor = BusinessLogicError
/**
 * 普通业务逻辑错误
 * 
 * 指除“未登陆”、“会话令牌过期”之外的其他逻辑错误
 * @param {string} message 
 * @param {number} errorCode
 * @param {JxbResponse} jxbResponse
 * @param {HttpResponse} httpResponse
 */
export function CommonBizLogicError(message, errorCode, jxbResponse, httpResponse = undefined) {
    this.name = 'CommonBizLogicError'
    this.message = message || '普通业务逻辑错误'
    this.stack = (new BusinessLogicError(message, errorCode, jxbResponse, httpResponse)).stack
    this.errorCode = errorCode
    this.jxbResponse = jxbResponse
    this.httpResponse = httpResponse
}
CommonBizLogicError.prototype = Object.create(BusinessLogicError.prototype)
CommonBizLogicError.prototype.constructor = CommonBizLogicError
/**
 * 须切换卖酒服务器错误
 * 
 * @param {string} message 
 * @param {number} errorCode
 * @param {JxbResponse} jxbResponse
 * @param {HttpResponse} httpResponse
 */
export function NeedToSwitchServerError(message, errorCode, jxbResponse, httpResponse = undefined) {
    this.name = 'NeedToSwitchServerError'
    this.message = message || '切换卖酒服务器'
    this.stack = (new BusinessLogicError(message, errorCode, jxbResponse, httpResponse)).stack
    this.errorCode = errorCode
    this.jxbResponse = jxbResponse
    this.httpResponse = httpResponse
}
NeedToSwitchServerError.prototype = Object.create(BusinessLogicError.prototype)
NeedToSwitchServerError.prototype.constructor = NeedToSwitchServerError
/**
 * 未登录账号错误
 * 
 * @param {string} message 
 * @param {number} errorCode
 * @param {JxbResponse} jxbResponse
 * @param {HttpResponse} httpResponse
 */
export function NotLoginError(message, errorCode, jxbResponse, httpResponse = undefined) {
    this.name = 'NotLoginError'
    this.message = message || '用户未登录'
    this.stack = (new BusinessLogicError(message, errorCode, jxbResponse, httpResponse)).stack
    this.errorCode = errorCode
    this.jxbResponse = jxbResponse
    this.httpResponse = httpResponse
}
NotLoginError.prototype = Object.create(BusinessLogicError.prototype)
NotLoginError.prototype.constructor = NotLoginError
/**
 * 会话过期或令牌无效错误
 * 
 * @param {string} message 
 * @param {number} errorCode
 * @param {JxbResponse} jxbResponse
 * @param {HttpResponse} httpResponse
 */
export function SessionExpiredError(message, errorCode, jxbResponse, httpResponse = undefined) {
    this.name = 'SessionExpiredError'
    this.message = message || '会话已过期'
    this.stack = (new BusinessLogicError(message, errorCode, jxbResponse, httpResponse)).stack
    this.errorCode = errorCode
    this.jxbResponse = jxbResponse
    this.httpResponse = httpResponse
}
SessionExpiredError.prototype = Object.create(BusinessLogicError.prototype)
SessionExpiredError.prototype.constructor = SessionExpiredError